﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;

namespace System.Linq
{

    public static class IQueryableExtensions
    {

        public static Expression<Func<TElement, bool>> BuildContainsExpression<TElement>(Expression valueSelector, ParameterExpression param, string value)
        {
            var containsMethod = typeof(string).GetMethod(nameof(string.Contains), new[] { typeof(string) });
            var contains = (Expression)Expression.Call(valueSelector, containsMethod, Expression.Constant(value, typeof(string)));
            return Expression.Lambda<Func<TElement, bool>>(contains, param);
        }

        public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, bool? acs = null)
        {
            var type = typeof(T);
            var property = type.GetProperty(ordering);
            var parameter = Expression.Parameter(type, "p");
            var propertyAccess = Expression.MakeMemberAccess(parameter, property);
            var orderByExp = Expression.Lambda(propertyAccess, parameter);
            MethodCallExpression resultExp;
            if (acs ?? true)
            {
                resultExp = Expression.Call(typeof(Queryable), "OrderBy", new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
            }
            else
            {
                resultExp = Expression.Call(typeof(Queryable), "OrderByDescending", new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
            }
            return source.Provider.CreateQuery<T>(resultExp);
        }

        public static IQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string ordering)
        {
            return OrderBy(source, ordering, false);
        }

        public static IQueryable<TElement> WhereOrLike<TElement>(this IQueryable<TElement> query, params FilterParameters[] filters)
        {
            var param = Expression.Parameter(typeof(TElement), "c");
            Expression body = null;
            foreach (var filter in filters)
            {
                var exp = BuildContainsExpression<TElement>(
                    Expression.Property(param, typeof(TElement).GetProperty(filter.Field)),
                        param, filter.Keyword).Body;
                if (!filter.IsIncluded)
                {
                    exp = Expression.Lambda<Func<TElement, bool>>(
                        Expression.Not(exp), param).Body;
                }
                if (body == null)
                {
                    body = exp;
                }
                else
                {
                    body = Expression.Or(body, exp);
                }
            }
            return query.Where(Expression.Lambda<Func<TElement, bool>>(body, param));
        }

        public static IQueryable<TElement> WhereAndAlsoLike<TElement>(this IQueryable<TElement> query, params FilterParameters[] filters)
        {
            var param = Expression.Parameter(typeof(TElement), "c");
            Expression body = null;
            foreach (var filter in filters)
            {
                var exp = BuildContainsExpression<TElement>(
                    Expression.Property(param, typeof(TElement).GetProperty(filter.Field)),
                        param, filter.Keyword).Body;
                if (!filter.IsIncluded)
                {
                    exp = Expression.Lambda<Func<TElement, bool>>(
                        Expression.Not(exp), param).Body;
                }
                if (body == null)
                {
                    body = exp;
                }
                else
                {
                    body = Expression.AndAlso(body, exp);
                }
            }
            return query.Where(Expression.Lambda<Func<TElement, bool>>(body, param));
        }

        public static IQueryable<TElement> WhereLike<TElement>(this IQueryable<TElement> query, params FilterParameters[] filters)
        {
            var param = Expression.Parameter(typeof(TElement), "c");
            Expression body = null;
            foreach (var filter in filters)
            {
                var exp = BuildContainsExpression<TElement>(
                    Expression.Property(param, typeof(TElement).GetProperty(filter.Field)),
                        param, filter.Keyword).Body;
                if (!filter.IsIncluded)
                {
                    exp = Expression.Lambda<Func<TElement, bool>>(
                        Expression.Not(exp), param).Body;
                }
                if (body == null)
                {
                    body = exp;
                }
                else
                {
                    if (filter.IsAnd)
                    {
                        body = Expression.AndAlso(body, exp);
                    }
                    else
                    {
                        body = Expression.OrElse(body, exp);
                    }
                }
            }
            return query.Where(Expression.Lambda<Func<TElement, bool>>(body, param));
        }

        public static IQueryable<TElement> Like<TElement>(this IQueryable<TElement> query, string field, string value, bool isIncluded = true)
        {
            var param = Expression.Parameter(typeof(TElement), "c");
            var body = BuildContainsExpression<TElement>(
                   Expression.Property(param, typeof(TElement).GetProperty(field)), param, value);
            if (!isIncluded)
            {
                body = Expression.Lambda<Func<TElement, bool>>(Expression.Not(body.Body), param);
            }
            return query.Where(body);
        }

    }

}